#pragma once
#include "Runtime/BattleFirePrefix.h"
#include "Runtime/Utils/ThreadSafeCounter.h"
#include "Thread.h"
#include "RunnableTaskEvent.h"
#include "Runtime/Log/Logger.h"

namespace Alice
{
	enum class ESubsequentsMode
	{
		/** Necessary when another task will depend on this task. */
		TrackSubsequents,
		/** Can be used to save task graph overhead when firing off a task that will not be a dependency of other tasks. */
		FireAndForget
	};
    class RunnableTask
	{
	protected:
	    RunnableTask(int32 inNumberOfPrerequistitesOutstanding)
	        : mThreadToExecuteOn(ENamedThreads::AnyThread)
	        , mNumberOfPrerequistitesOutstanding(inNumberOfPrerequistitesOutstanding + 1) // + 1 is not a prerequisite, it is a lock to prevent it from executing while it is getting prerequisites, one it is safe to execute, call PrerequisitesComplete
	    {
	    }
	    void SetThreadToExecuteOn(ENamedThreads InThreadToExecuteOn)
	    {
	        mThreadToExecuteOn = InThreadToExecuteOn;
	    }
	    void PrerequisitesComplete(ENamedThreads CurrentThread, int32 NumAlreadyFinishedPrequistes, bool bUnlock = true)
	    {
	        int32 NumToSub = NumAlreadyFinishedPrequistes + (bUnlock ? 1 : 0); // the +1 is for the "lock" we set up in the constructor
	        if (mNumberOfPrerequistitesOutstanding.Subtract(NumToSub) == NumToSub) 
	        {
	            QueueTask(CurrentThread);
	        }
	    }
	    virtual ~RunnableTask()
	    {
	    }
	public:
   		void ConditionalQueueTask(ENamedThreads CurrentThread)
   		{
   			if (mNumberOfPrerequistitesOutstanding.Decrement()==0)
   			{
   				QueueTask(CurrentThread);
   			}
   		}
	    virtual void ExecuteTask(std::vector<RunnableTask*>& NewTasks, ENamedThreads CurrentThread)=0;
	    FORCEINLINE void Execute(std::vector<RunnableTask*>& NewTasks, ENamedThreads CurrentThread)
	    {
	        ExecuteTask(NewTasks, CurrentThread);
	    }
	    void QueueTask(ENamedThreads CurrentThreadIfKnown);
	    ENamedThreads				mThreadToExecuteOn;
	    ThreadSafeCounter			mNumberOfPrerequistitesOutstanding;
	};
	template<typename SomeRunnableTaskType>
	class TRunnableTask final : public RunnableTask
	{
	public:
		/** 
		 *	This is a helper class returned from the factory. It constructs the embeded task with a set of arguments and sets the task up and makes it ready to execute.
		 *	The task may complete before these routines even return.
		 **/
		class FConstructor
		{
		public:
			/** Passthrough internal task constructor and dispatch. Note! Generally speaking references will not pass through; use pointers */
			template<typename...T>
			RunnableTaskEventRef ConstructAndDispatchWhenReady(T&&... Args)
			{
				new ((void *)&Owner->TaskStorage) SomeRunnableTaskType(std::forward<T>(Args)...);
				return Owner->Setup(Prerequisites, FireThread);
			}

			/** Passthrough internal task constructor and hold. */
			template<typename...T>
			TRunnableTask* ConstructAndHold(T&&... Args)
			{
				new ((void *)&Owner->TaskStorage) SomeRunnableTaskType(std::forward<T>(Args)...);
				return Owner->Hold(Prerequisites, FireThread);
			}

		private:
			friend class TRunnableTask;

			/** The task that created me to assist with embeded task construction and preparation. **/
			TRunnableTask*								Owner;
			/** The list of prerequisites. **/
			const RunnableTaskEventArray*			Prerequisites;
			/** If known, the current thread.  ENamedThreads::AnyThread is also fine, and if that is the value, we will determine the current thread, as needed, via TLS. **/
			ENamedThreads								FireThread;

			/** Constructor, simply saves off the arguments for later use after we actually construct the embeded task. **/
			FConstructor(TRunnableTask* InOwner, const RunnableTaskEventArray* InPrerequisites, ENamedThreads InFireThread)
				: Owner(InOwner)
				, Prerequisites(InPrerequisites)
				, FireThread(InFireThread)
			{
			}
			/** Prohibited copy construction **/
			FConstructor(const FConstructor& Other){}
			/** Prohibited copy **/
			void operator=(const FConstructor& Other){}
		};

		/** 
		 *	Factory to create a task and return the helper object to construct the embedded task and set it up for execution.
		 *	@param Prerequisites; the list of FGraphEvents that must be completed prior to this task executing.
		 *	@param CurrentThreadIfKnown; provides the index of the thread we are running on. Can be ENamedThreads::AnyThread if the current thread is unknown.
		 *	@return a temporary helper class which can be used to complete the process.
		**/
		static FConstructor CreateTask(const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			int32 NumPrereq = Prerequisites ? Prerequisites->size() : 0;
			return FConstructor(new TRunnableTask(SomeRunnableTaskType::GetSubsequentsMode() == ESubsequentsMode::FireAndForget ? NULL : RunnableTaskEvent::CreateGraphEvent(), NumPrereq), Prerequisites, CurrentThreadIfKnown);
		}

		void Unlock(ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			ConditionalQueueTask(CurrentThreadIfKnown);
		}

		RunnableTaskEventRef GetCompletionEvent()
		{
			return Subsequents;
		}

	private:
		friend class FConstructor;
		friend class AliceThreadPoolEvent;
		void ExecuteTask(std::vector<RunnableTask*>& NewTasks, ENamedThreads CurrentThread)
		{
			if (SomeRunnableTaskType::GetSubsequentsMode() == ESubsequentsMode::TrackSubsequents)
			{
				Subsequents->CheckDontCompleteUntilIsEmpty(); // we can only add wait for tasks while executing the task
			}
			SomeRunnableTaskType& Task = *(SomeRunnableTaskType*)&TaskStorage;
			{
				Task.DoTask(CurrentThread, Subsequents);
				Task.~SomeRunnableTaskType();
			}
			
			TaskConstructed = false;

			if (SomeRunnableTaskType::GetSubsequentsMode() == ESubsequentsMode::TrackSubsequents)
			{
				PlatformMisc::MemoryBarrier();
				Subsequents->DispatchSubsequents(NewTasks, CurrentThread);
			}
			delete this;
		}
		TRunnableTask(RunnableTaskEventRef InSubsequents, int32 NumberOfPrerequistitesOutstanding)
			: RunnableTask(NumberOfPrerequistitesOutstanding)
			, TaskConstructed(false)
		{
			std::swap(Subsequents,InSubsequents);
		}
		~TRunnableTask() override{}
		void SetupPrereqs(const RunnableTaskEventArray* Prerequisites, ENamedThreads CurrentThreadIfKnown, bool bUnlock)
		{
			TaskConstructed = true;
			SomeRunnableTaskType& Task = *(SomeRunnableTaskType*)&TaskStorage;
			SetThreadToExecuteOn(Task.GetDesiredThread());
			uint32 AlreadyCompletedPrerequisites = 0;
			if (Prerequisites)
			{
				for (uint32 Index = 0; Index < Prerequisites->size(); Index++)
				{
					const RunnableTaskEventRef & Prerequisite = (*Prerequisites)[Index];
					if (Prerequisite == nullptr || !Prerequisite->AddSubsequent(this))
					{
						AlreadyCompletedPrerequisites++;
					}
				}
			}
			PrerequisitesComplete(CurrentThreadIfKnown, AlreadyCompletedPrerequisites, bUnlock);
		}
		RunnableTaskEventRef Setup(const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			RunnableTaskEventRef ReturnedDependencyPtr = Subsequents; // very important so that this doesn't get destroyed before we return
			SetupPrereqs(Prerequisites, CurrentThreadIfKnown, true);
			return ReturnedDependencyPtr;
		}
		TRunnableTask* Hold(const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			SetupPrereqs(Prerequisites, CurrentThreadIfKnown, false);
			return this;
		}
	public:
		static FConstructor CreateTask(RunnableTaskEventRef SubsequentsToAssume, const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			return FConstructor(new TRunnableTask(SubsequentsToAssume, Prerequisites ? Prerequisites->size() : 0), Prerequisites, CurrentThreadIfKnown);
		}
		TAlignedBytes<sizeof(SomeRunnableTaskType),alignof(SomeRunnableTaskType)>	TaskStorage;
		bool																		TaskConstructed;
		RunnableTaskEventRef													Subsequents;
	};
	class ThreadReturnTask
	{
	public:
		ThreadReturnTask(ENamedThreads InThreadToReturnFrom)
			: mThreadToReturn(InThreadToReturnFrom)
		{
		}
		ENamedThreads GetDesiredThread()
		{
			return mThreadToReturn;
		}
		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent);

	private:
		ENamedThreads mThreadToReturn;
	};
	class FTriggerEventGraphTask
	{
	public:
		/** 
		*	Constructor
		*	@param InScopedEvent; Scoped event to fire
		**/
		FTriggerEventGraphTask(Event* InEvent, ENamedThreads InDesiredThread = ENamedThreads::AnyHiPriThreadHiPriTask)
			: Event(InEvent) 
			, DesiredThread(InDesiredThread)
		{
		}
		ENamedThreads GetDesiredThread()
		{
			return DesiredThread;
		}

		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }

		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent)
		{
			Event->TriggerAll();
		}
	private:
		Event* Event;
		/** Thread to run on, can be ENamedThreads::AnyThread **/
		ENamedThreads DesiredThread;
	};
	class FNullGraphTask
	{
	public:
		/** 
		*	Constructor
		*	@param StatId The stat id for this task.
		*	@param InDesiredThread; Thread to run on, can be ENamedThreads::AnyThread
		**/
		FNullGraphTask(ENamedThreads InDesiredThread)
			: DesiredThread(InDesiredThread)
		{
		}

		/** 
		*	Retrieve the thread that this task wants to run on.
		*	@return the thread that this task should run on.
		**/
		ENamedThreads GetDesiredThread()
		{
			return DesiredThread;
		}

		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }

		/** 
		*	Actually execute the task.
		*	@param	CurrentThread; the thread we are running on
		*	@param	MyCompletionGraphEvent; my completion event. Not always useful since at the end of DoWork, you can assume you are done and hence further tasks do not need you as a prerequisite. 
		*	However, MyCompletionGraphEvent can be useful for passing to other routines or when it is handy to set up subsequents before you actually do work.
		**/
		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent)
		{
		}
	private:
		/** Thread to run on, can be ENamedThreads::AnyThread **/
		ENamedThreads DesiredThread;
	};
	class FinishThreadWorkTask
	{
	public:
		FinishThreadWorkTask(Event* InEvent, ENamedThreads InDesiredThread = ENamedThreads::AnyHiPriThreadHiPriTask)
			: mEvent(InEvent)
			, DesiredThread(InDesiredThread)
		{
		}
		ENamedThreads GetDesiredThread()
		{
			return DesiredThread;
		}

		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::FireAndForget; }

		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent)
		{
			mEvent->TriggerAll();
		}
	private:
		SmartPtr<Event> mEvent;
		ENamedThreads DesiredThread;
	};
}
